package org.processmining.plugins.complianceframework;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import org.processmining.plugins.DataConformance.Alignment;
import org.processmining.plugins.DataConformance.ResultReplay;
import org.processmining.plugins.DataConformance.DataAlignment.PetriNet.ResultReplayPetriNetWithData;
import org.processmining.plugins.DataConformance.framework.ExecutionStep;
import org.processmining.plugins.DataConformance.visualization.DataAwareStepTypes;
import org.processmining.plugins.petrinet.replayresult.PNRepResult;

import weka.classifiers.trees.J48;
import weka.core.Attribute;
import weka.core.DenseInstance;
import weka.core.Instance;
import weka.core.Instances;

public class InstanceClassificatior {
	private static final String[] classAttributeVals = new String[] {
			"log-only", "model-only", "incorrect data", "incorrect time",
			"log-only with incorrect time", "model-only with incorrect time",
			"incorrect data and time", "non-problematic" };

	private Map<String, List<String>> attributeAssignments;
	private List<String> activitiesProblems;
	// private List<String> activities;
	private Map<String, List<String>> attributePossibleVals;
	private Map<String, float[]> intervals;
	private J48 tree = new J48();

	private PNRepResult controFlowAlignment = null;
	private ResultReplayPetriNetWithData replayResultWithData = null;
	private ResultReplay replayResultTemporal = null;
	private ComplianceDashboard dashboard;

	private Instances instances;

	// TODO: do we need control flow alignment?
	// the value with index 0 in attributePossibleVals must be empty String for
	// each attribute
	public InstanceClassificatior(ComplianceDashboard dashboard,
			Map<String, List<String>> attributeAssignments,
			List<String> activitiesProblems,
			Map<String, List<String>> attributePossibleVals,
			Map<String, float[]> intervals) {
		this.attributeAssignments = attributeAssignments;
		this.activitiesProblems = activitiesProblems;
		this.controFlowAlignment = dashboard.getControFlowAlignment();
		this.replayResultWithData = dashboard.getReplayResultWithData();
		this.replayResultTemporal = dashboard.getReplayResultTemporal();
		this.dashboard = dashboard;
		this.attributePossibleVals = attributePossibleVals;
		this.intervals = intervals;

		this.createInctances();
		this.generateTree();
	}

	// 1 instance == 1 event
	// instance attributes: event name; values of all event attributes except
	// timestamp; meets temporal conditions or not; class attribute: violating
	// or not
	private void createInctances() {
		ArrayList<Attribute> attrList = new ArrayList<Attribute>();

		Attribute classAttribute = new Attribute("Problem type",
				Arrays.asList(classAttributeVals));
		Attribute activityAttribute;

		List<String> activitiesList = new ArrayList<String>();
		for (String activity : replayResultWithData.actArray.keySet()) {
			int[] activityStats = this.dashboard.getActivityMovesMap().get(
					activity);
			int totalActivityMoves = activityStats[0] + activityStats[1]
					+ activityStats[activityStats.length - 2];
			//only add activity if it has visible moves
			if (totalActivityMoves > activityStats[0])
				activitiesList.add(activity);
		}
		activityAttribute = new Attribute("Activity", activitiesList);
		// }
		int i = 0;
		for (String attrName : replayResultWithData.attrArray.keySet()) {
			if (!intervals.containsKey(attrName))
				attrList.add(new Attribute(attrName, this.attributePossibleVals
						.get(attrName)));
			else
				attrList.add(new Attribute(attrName));
			i++;
		}

		attrList.add(0, activityAttribute);
		attrList.add(classAttribute);
		this.instances = new Instances("StepInstances", attrList, 0);
		this.instances.setClassIndex(attrList.size() - 1);

		for (Alignment dataAlignment : this.replayResultWithData.labelStepArray) {
			for (i = 0; i < dataAlignment.getLogTrace().size(); i++) {
				if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MINVI)
					continue;
				ExecutionStep currentStep;
				if (dataAlignment.getStepTypes().get(i) == DataAwareStepTypes.MREAL)
					currentStep = dataAlignment.getProcessTrace().get(i);
				else
					currentStep = dataAlignment.getLogTrace().get(i);
				if (!includeStepInInstances(currentStep))
					continue;
				double[] instanceValue = new double[this.instances
						.numAttributes()];

				instanceValue[0] = activitiesList.indexOf(currentStep
						.getActivity().toString());

				for (int j = 1; j < instanceValue.length - 1; j++) {
					if (currentStep.containsKey(attrList.get(j).name())) {
						if (currentStep.get(attrList.get(j).name()) instanceof Number)
							instanceValue[j] = ((Number) currentStep
									.get(attrList.get(j).name())).doubleValue();
						else
							instanceValue[j] = this.attributePossibleVals.get(
									attrList.get(j).name()).indexOf(
									currentStep.get(attrList.get(j).name())
											.toString());

					}
				}
				int classValue;
				if (this.replayResultTemporal != null) {
					Alignment timeAlignment = findAlignmentWithLabel(
							dataAlignment.getTraceName(),
							this.replayResultTemporal.labelStepArray);
					classValue = getClassAttrValue(dataAlignment.getStepTypes()
							.get(i), timeAlignment.getStepTypes().get(i));
				} else {
					classValue = getClassAttrValue(dataAlignment.getStepTypes()
							.get(i));
				}
				instanceValue[instanceValue.length - 1] = classValue;
				// instance.setValue(classAttribute, classValue);
				// instanceList.add(instance);
				instances.add(new DenseInstance(1.0, instanceValue));
			}
		}
	}

	private int getClassAttrValue(DataAwareStepTypes dataStepType,
			DataAwareStepTypes timeStepType) {
		if (dataStepType == DataAwareStepTypes.L
				&& timeStepType != DataAwareStepTypes.LMGOOD) {
			return 4;
		} else if (dataStepType == DataAwareStepTypes.MREAL
				&& timeStepType != DataAwareStepTypes.LMGOOD) {
			return 5;
		} else if (dataStepType == DataAwareStepTypes.LMNOGOOD
				&& timeStepType != DataAwareStepTypes.LMGOOD) {
			return 6;
		} else if (dataStepType == DataAwareStepTypes.L) {
			return 0;
		} else if (dataStepType == DataAwareStepTypes.MREAL) {
			return 1;
		} else if (dataStepType == DataAwareStepTypes.LMNOGOOD) {
			return 2;
		} else if (timeStepType != DataAwareStepTypes.LMGOOD) {
			return 3;
		} else {
			return 7;
		}
	}

	private int getClassAttrValue(DataAwareStepTypes dataStepType) {
		if (dataStepType == DataAwareStepTypes.L) {
			return 0;
		} else if (dataStepType == DataAwareStepTypes.MREAL) {
			return 1;
		} else if (dataStepType == DataAwareStepTypes.LMNOGOOD) {
			return 2;
		} else {
			return 7;
		}
	}

	private boolean includeStepInInstances(ExecutionStep step) {
		boolean activityMatch, attrAssignmentMatch;
		if (activitiesProblems != null && activitiesProblems.size() > 0
				&& activitiesProblems.contains(step.getActivity()))
			activityMatch = true;
		else if (activitiesProblems != null && activitiesProblems.size() > 0)
			activityMatch = false;
		else
			activityMatch = true;

		if (attributeAssignments.keySet().isEmpty()) {
			attrAssignmentMatch = true;
		} else {
			attrAssignmentMatch = true;
			for (String attrName : attributeAssignments.keySet()) {
				if (!step.containsKey(attrName) || step.get(attrName) == null) {
					attrAssignmentMatch = false;
					break;
				} else if (intervals.containsKey(attrName)) {
					boolean intervalMatch = false;
					for (String interval : attributeAssignments.get(attrName))
						if (isInInterval(attrName,
								((Number) step.get(attrName)).floatValue(),
								interval)) {
							intervalMatch = true;
							break;
						}
					if (!intervalMatch) {
						attrAssignmentMatch = false;
						break;
					}
				} else if (!attributeAssignments.get(attrName).contains(
						step.get(attrName).toString())) {
					attrAssignmentMatch = false;
					break;
				}
			}
		}

		// if (activitiesProblems.get(step.getActivity()).length < 4)
		// return false;
		// if (timeStepType == DataAwareStepTypes.LMGOOD)
		// return activitiesProblems.get(step.getActivity())[3] == false;
		// else
		// return activitiesProblems.get(step.getActivity())[3] == true;

		return activityMatch || attrAssignmentMatch;
	}

	// private boolean isStepTimeProblematic(ExecutionStep step,
	// DataAwareStepTypes timeStepType) {
	//
	// }

	private boolean isInInterval(String attribute, float value, String interval) {
		if (!intervals.containsKey(attribute))
			return false;
		boolean result = false;
		int intervalIndex = Integer.parseInt(interval);
		if (intervalIndex == 0)
			result = value <= intervals.get(attribute)[intervalIndex];
		else
			result = value <= intervals.get(attribute)[intervalIndex]
					&& value > intervals.get(attribute)[intervalIndex - 1];
		return result;
	}

	private Alignment findAlignmentWithLabel(String label,
			Collection<Alignment> labelStepArray) {
		Alignment result = null;
		for (Alignment alignment : labelStepArray)
			if (alignment.getTraceName().equalsIgnoreCase(label)) {
				result = alignment;
				break;
			}
		return result;
	}

	private void generateTree() {
		this.tree = new J48();
		try {
			this.tree.buildClassifier(this.instances);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public String getGraph() {
		try {
			return this.tree.graph();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	private void removeInsignificantAttributes() {

	}
}